/*
 * Copyright 2013 JROSE
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jrose.service.loader.internal;

import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.jrose.api.annotation.common.ConfigurationLocation;
import org.jrose.api.annotation.remote.ExposeHttpService;
import org.jrose.core.util.CollectionUtils;
import org.jrose.core.util.XMLUtils;
import org.jrose.entity.config.Career;
import org.jrose.entity.config.CareerAttributeBonus;
import org.jrose.entity.config.CareerAttributeLimit;
import org.jrose.entity.constant.Race;
import org.jrose.entity.data.Role;
import org.jrose.entity.data.RoleCareer;
import org.jrose.service.loader.AbstractStandardConfigurationLoader;
import org.jrose.service.loader.CareerLoader;
import org.w3c.dom.Element;

import javax.inject.Named;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import static org.jrose.core.util.ConversionUtils.toBoolean;
import static org.jrose.core.util.ConversionUtils.toInt;
import static org.jrose.core.util.ConversionUtils.toLong;

/**
 * Default {@link CareerLoader} implementation.
 *
 * @author Xiaohai Zhang
 * @since 2013-08-27 12:25
 */
@Named
@NoArgsConstructor
@ConfigurationLocation("/config/career-configuration.xml")
@AbstractStandardConfigurationLoader.StandardConfiguration("career")
@ExposeHttpService(name = "/careerLoader", serviceInterface = CareerLoader.class)
public class CareerLoaderImpl extends AbstractStandardConfigurationLoader<Career> implements CareerLoader {
    private Map<Integer, Career> careers = new LinkedHashMap<>();
    private Career initialCareer;
    private Career monsterCareer;
    private Map<Integer, List<Career>> groupedCareers = new LinkedHashMap<>();

    @Override
    protected Career parseElement(Element element) {
        Career career = new Career();
        if (element.hasAttribute("initial")) {
            career.setInitialCareer(toBoolean(element.getAttribute("initial")));
        }
        if (element.hasAttribute("monster")) {
            career.setMonsterCareer(toBoolean(element.getAttribute("monster")));
        }
        career.setGroupId(toInt(element.getAttribute("group")));

        career.setId(toInt(XMLUtils.getChildElementText(element, "id")));
        career.setName(XMLUtils.getChildElementText(element, "name"));
        career.setDescription(StringUtils.defaultString(XMLUtils.getChildElementText(element, "description")));

        career.setDisplayNames(new LinkedHashMap<Race, String>());
        Element displayNamesElement = XMLUtils.getSingleChildElement(element, "display-names");
        if (displayNamesElement != null) {
            for (Element displayNameElement : XMLUtils.getChildElements(displayNamesElement, "display-name")) {
                Race race = Race.valueOf(displayNameElement.getAttribute("race-id"));
                career.getDisplayNames().put(race, displayNameElement.getAttribute("name"));
            }
        }

        career.setBonus(parseCareerAttributeBonus(XMLUtils.getSingleChildElement(element, "attribute-bonus")));
        career.setLimit(parseCareerAttributeLimit(XMLUtils.getSingleChildElement(element, "attribute-limit")));

        career.setRequiredCareerIds(new LinkedList<Integer>());
        Element restrictElement = XMLUtils.getSingleChildElement(element, "restrict");
        if (restrictElement != null) {
            Element requiredCareersElement = XMLUtils.getSingleChildElement(restrictElement, "required-careers");
            if (requiredCareersElement != null) {
                List<Element> careerIdElements = XMLUtils.getChildElements(requiredCareersElement, "career-id");
                for (Element careerIdElement : careerIdElements) {
                    career.getRequiredCareerIds().add(toInt(careerIdElement.getTextContent()));
                }
            }
        }

        career.setAvailableSpellIds(new LinkedList<Integer>());
        Element spellsElement = XMLUtils.getSingleChildElement(element, "spells");
        for (Element spellIdElement : XMLUtils.getChildElements(spellsElement, "spell-id")) {
            career.getAvailableSpellIds().add(toInt(spellIdElement.getTextContent()));
        }

        career.setMaster(toLong(XMLUtils.getChildElementText(element, "master")));
        career.setDamage(toInt(XMLUtils.getChildElementText(element, "damage")));
        return career;
    }

    @Override
    protected void processConfigurations(List<Career> configurations) {
        for (Career career : configurations) {
            careers.put(career.getId(), career);
            if (career.isInitialCareer()) {
                Validate.isTrue(initialCareer == null, "Duplicated initial career found");
                initialCareer = career;
            }
            if (career.isMonsterCareer()) {
                Validate.isTrue(monsterCareer == null, "Duplicated monster career found");
                monsterCareer = career;
            }
            if (groupedCareers.containsKey(career.getGroupId())) {
                groupedCareers.get(career.getGroupId()).add(career);
            } else {
                List<Career> groupedCareer = new LinkedList<>();
                groupedCareer.add(career);
                groupedCareers.put(career.getGroupId(), groupedCareer);
            }
        }
        Validate.notNull(initialCareer, "No initial career found");
        Validate.notNull(monsterCareer, "No monster career found");
    }

    @Override
    public Career load(int id) {
        initializeLoader();
        return careers.get(id);
    }

    @Override
    public Career loadInitialCareer() {
        initializeLoader();
        return initialCareer;
    }

    @Override
    public Career loadMonsterCareer() {
        initializeLoader();
        return monsterCareer;
    }

    @Override
    public List<Career> loadByGroupId(int groupId) {
        initializeLoader();
        return groupedCareers.get(groupId);
    }

    @Override
    public List<Career> loadRoleCareers(Role role) {
        if (role == null) {
            return Collections.emptyList();
        }
        List<RoleCareer> roleCareers = roleCareerPersistence.findByRoleId(role.getId());
        List<Career> careers = new LinkedList<>();
        for (RoleCareer roleCareer : roleCareers) {
            CollectionUtils.addNonNullElement(careers, load(roleCareer.getCareerId()));
        }
        return careers;
    }

    // private methods:

    private CareerAttributeBonus parseCareerAttributeBonus(Element element) {
        CareerAttributeBonus bonus = new CareerAttributeBonus();
        bonus.setAttackBonus(toInt(element.getAttribute("AP")));
        bonus.setDefenseBonus(toInt(element.getAttribute("DP")));
        bonus.setSpiritAttackBonus(toInt(element.getAttribute("SP")));
        bonus.setSpiritDefenseBonus(toInt(element.getAttribute("SD")));
        bonus.setSpeedBonus(toInt(element.getAttribute("SPD")));
        return bonus;
    }

    private CareerAttributeLimit parseCareerAttributeLimit(Element element) {
        CareerAttributeLimit limit = new CareerAttributeLimit();
        limit.setHealthLimit(toInt(element.getAttribute("health")));
        limit.setManaLimit(toInt(element.getAttribute("mana")));
        limit.setAttackLimit(toInt(element.getAttribute("AP")));
        limit.setDefenseLimit(toInt(element.getAttribute("DP")));
        limit.setSpiritAttackLimit(toInt(element.getAttribute("SP")));
        limit.setSpiritDefenseLimit(toInt(element.getAttribute("SD")));
        limit.setSpeedLimit(toInt(element.getAttribute("SPD")));
        return limit;
    }
}
